package com.example.ass7.level;

import java.awt.Color;

import biuoop.DrawSurface;
import biuoop.KeyboardSensor;

import com.example.ass7.GameEnvironment;
import com.example.ass7.animation.Animation;
import com.example.ass7.animation.AnimationRunner;
import com.example.ass7.animation.CountdownAnimation;
import com.example.ass7.animation.KeyPressStoppableAnimation;
import com.example.ass7.animation.PauseScreen;
import com.example.ass7.collision.BallRemover;
import com.example.ass7.collision.BlockRemover;
import com.example.ass7.collision.Collidable;
import com.example.ass7.collision.PlayerKiller;
import com.example.ass7.collision.PrintingHitListener;
import com.example.ass7.collision.ScoreTrackingListener;
import com.example.ass7.dataloader.Background;
import com.example.ass7.geometryprimitives.Counter;
import com.example.ass7.geometryprimitives.Point;
import com.example.ass7.geometryprimitives.Velocity;
import com.example.ass7.sprite.Ball;
import com.example.ass7.sprite.Block;
import com.example.ass7.sprite.Formation;
import com.example.ass7.sprite.LevelNameIndicator;
import com.example.ass7.sprite.LivesIndicator;
import com.example.ass7.sprite.Paddle;
import com.example.ass7.sprite.ScoreIndicator;
import com.example.ass7.sprite.Sprite;
import com.example.ass7.sprite.SpriteCollection;

/**
 * @author Meirav Ben Izhak
 */
public class GameLevel implements Animation {

    private final SpriteCollection sprites;
    private final GameEnvironment environment;
    private Paddle paddle;
    private double yPaddle = 565;
    private final Counter remainingBlocks;
    private final Counter availableBalls;
    private final Counter currentScore;
    private final Counter numberOfLives;
    private final AnimationRunner runner;
    private boolean running;
    private final KeyboardSensor keyboard;
    private final LevelInformation levelInfo;
    private long startShootTime = 0;
    private long startAlienShootTime = 0;
    private Formation formation;

    /**
     * @param levelInfo
     *            levelInfo
     * @param keyboardSensor
     *            keyboardSensor
     * @param animationRunner
     *            animationRunner
     * @param currentScore
     *            currentScore
     * @param numberOfLives
     *            numberOfLives
     */
    public GameLevel(
        LevelInformation levelInfo, KeyboardSensor keyboardSensor,
        AnimationRunner animationRunner, Counter currentScore,
        Counter numberOfLives) {
        this.levelInfo = levelInfo;
        this.keyboard = keyboardSensor;
        this.environment = new GameEnvironment();
        this.sprites = new SpriteCollection();
        this.remainingBlocks = new Counter();
        this.availableBalls = new Counter();
        this.currentScore = currentScore;
        this.numberOfLives = numberOfLives;
        this.runner = animationRunner;
        this.running = false;
    }

    /**
     * @param c
     *            c
     */
    public void addCollidable(Collidable c) {
        this.environment.addCollidable(c);
    }

    /**
     * @param s
     *            s
     */
    public void addSprite(Sprite s) {
        this.sprites.addSprite(s);
    }

    /**
     * @param c
     *            c
     */
    public void removeCollidable(Collidable c) {
        this.environment.setCollidablesAmount(this.environment
                .getCollidablesAmount() - 1);
        this.environment.getGamesCollidables().remove(c);
    }

    /**
     * @param s
     *            s
     */
    public void removeSprite(Sprite s) {
        this.sprites.getSpritesList().remove(s);
        this.sprites.setHowManySprites(this.sprites.getHowManySprites() - 1);
    }

    /**
     * void.
     */
    public void setBlocks() {

        BlockRemover blockRemove = new BlockRemover(this);
        for (Block block : this.levelInfo.blocks()) {
            block.addToGame(this);
            block.setHitPoints(block.getOriginalHitPoints());
            block.addHitListener(blockRemove);
        }
    }

    /**
     * @param speed speed
     */
    public void setFormation(double speed) {
        ScoreTrackingListener scoreTrackingListener = new ScoreTrackingListener(
                this.currentScore);

        formation = this.levelInfo.formation();
        formation.setSpeed(speed);
        formation.addToGame(this);
        remainingBlocks.increase(formation.getRemainingAlienSize());
        BlockRemover blockRemove =
                new BlockRemover(this, this.remainingBlocks, formation);
        formation.addHitListener(blockRemove);
        formation.addHitListener(scoreTrackingListener);

    }

    // Initialize a new game: create the Blocks and Ball (and Paddle)
    // and add them to the game.
    /**
     * @param alienSpeed alienSpeed
     * @param levelNumber levelNumber
     */
    public void initialize(double alienSpeed, int levelNumber) {
        this.initBoardGame(alienSpeed, levelNumber);
    }

    /**
     * void.
     */
    private void setSurroundingBlocks() {
        // setUpperBlock
        Block up = new Block(new Point(0, 0), 800, 30, new Background(null,
                Color.WHITE));
        up.setHitPoints(0);
        up.addToGame(this);
        up.addHitListener(new BallRemover(this, this.availableBalls));

        Block right = new Block(new Point(799, 0), 1, 600, new Background(
                null, Color.GRAY));
        right.setHitPoints(0);
        right.addToGame(this);

        Block down = new Block(new Point(0, 600), 800, 1, new Background(
                null, Color.GRAY));
        down.setHitPoints(0);
        down.addToGame(this);
        down.addHitListener(new BallRemover(this, this.availableBalls));

        Block left = new Block(new Point(0, 0), 1, 600, new Background(null,
                Color.GRAY));
        left.setHitPoints(0);
        left.addToGame(this);
    }

    /**
     * @param alienSpeed alienSpeed
     */
    public void playOneTurn(double alienSpeed) {
        // availableBalls.increase(levelInfo.numberOfBalls());
        this.paddle.setPosition(new Point(
                400 - (this.levelInfo.paddleWidth() / 2), yPaddle),
                this.levelInfo
                .paddleWidth());

        removeAllShoot();
        formation.begin(alienSpeed);

        // countdown before turn starts.
        this.runner.run(new CountdownAnimation(3.0, 3, this.sprites));
        this.running = true;
        // use our runner to run the current animation -- which is one turn of
        // the game.
        this.runner.run(this);

    }

    /**
     * void.
     */
    private void removeAllShoot() {
        for (int index = sprites.getHowManySprites() - 1; index >= 0; index--) {
            Sprite sprite = sprites.getSpritesList().get(index);
            if (sprite instanceof Ball) {
                sprites.removeSprite(sprite);
            }
        }
    }

    /**
     * void.
     */
    private void playerShoot() {
        Ball ball = new Ball(paddle.getCenterTopPosition(),
                3, new Velocity(0, -500), Color.WHITE);
        ball.setGameEnvironment(this.environment);
        ball.addToGame(this);
    }

    /**
     * void.
     */
    private void alienRandomShooting() {
        long endAlienShootTime = System.currentTimeMillis(); // timing
        if (endAlienShootTime - startAlienShootTime >= 500) {
            Block alien = formation.getRandomAlienShootOut();
            Ball ball = new Ball(alien.getCenterTopPosition(),
                    5, new Velocity(0, 300), Color.RED);
            ball.setAlienOwn(Boolean.TRUE);
            ball.setGameEnvironment(this.environment);
            ball.addToGame(this);
            startAlienShootTime = endAlienShootTime;
        }
    }

    /**
     * @param number number
     */
    public void decreaseLive(int number) {
        numberOfLives.decrease(number);
    }

    /**
     * @param alienSpeed alienSpeed
     * @param levelNumber levelNumber
     */
    private void initBoardGame(double alienSpeed, int levelNumber) {
        // init base
        this.environment.clearCollidableList();
        this.sprites.clearAll();
        this.remainingBlocks.increase(-this.remainingBlocks.getValue());

        Sprite background = this.levelInfo.getBackground();
        this.addSprite(background);

        this.setBlocks();
        this.setFormation(alienSpeed);
        this.setSurroundingBlocks();
        this.createPaddle();
        // this.createBallsOnTopOfPaddle();

        PrintingHitListener pHl = new PrintingHitListener();
        for (int iter = 0;
                iter < this.environment.getCollidablesAmount();
                iter++) {
            this.environment.getGamesCollidables().get(iter)
                    .addHitListener(pHl);
        }

        // initializes lives indicator
        LivesIndicator livesIndicator = new LivesIndicator(this.numberOfLives);
        livesIndicator.addToGame(this);

        // initializes score indicator
        ScoreIndicator scoreIndicator = new ScoreIndicator(this.currentScore);
        scoreIndicator.addToGame(this);

        // initializes score indicator
        LevelNameIndicator levelNameIndicator = new LevelNameIndicator(
                this.levelInfo.levelName() + String.valueOf(levelNumber));
        levelNameIndicator.addToGame(this);
    }

    /**
     * void.
     */
    private void createPaddle() {
        PlayerKiller playerKiller = new PlayerKiller(this, this.numberOfLives);

        this.paddle = new Paddle(this.keyboard, new Point(
                400 - (this.levelInfo.paddleWidth() / 2), yPaddle),
                this.levelInfo.paddleWidth(), this.levelInfo.paddleSpeed());
        this.paddle.addToGame(this);
        this.paddle.addHitListener(playerKiller);
    }

    /**
     * void.
     */
    public void stop() {
        this.running = Boolean.FALSE;
    }

    @Override
    public void doOneFrame(DrawSurface d,
            double dt) {
        this.sprites.drawAllOn(d);
        this.sprites.notifyAllTimePassed(dt);

        if (this.remainingBlocks.getValue() == 0) {
            this.running = Boolean.FALSE;
        }

        alienRandomShooting();

        if (this.keyboard.isPressed(KeyboardSensor.SPACE_KEY)) {
            long endShootTime = System.currentTimeMillis(); // timing
            if (endShootTime - startShootTime >= 320) {
                playerShoot();
                startShootTime = endShootTime;
            }
        }

        if (this.keyboard.isPressed("p")) {
            this.runner.run(new KeyPressStoppableAnimation(this.keyboard,
                    KeyboardSensor.SPACE_KEY, new PauseScreen()));
        }
    }

    /**
     * @return boolean
     */
    public boolean hasMoreBalls() {
        return this.availableBalls.getValue() > 0;
    }

    /**
     * @return boolean
     */
    public boolean hasMoreAliens() {
        return this.remainingBlocks.getValue() > 0;
    }

    /**
     * @return boolean
     */
    public boolean noMoreLives() {
        return this.numberOfLives.getValue() <= 0;
    }

    @Override
    public boolean shouldStop() {
        return !this.running;
    }
}
